package graphics;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;

import org.javia.arity.Function;
import org.javia.arity.Symbols;
import org.javia.arity.SyntaxException;


public class DemoFrame extends JInternalFrame {

	private final static String default_functionCode = "3.0*sin(x*cos(y))";
	private final static String default_dxCode = "(3.*cos(x*cos(y))*cos(y))";
	private final static String default_dzCode = "(-3.*cos(x*cos(y))*x*sin(y))";
	//final int btsz = 100;
	final int pad = 5;
	final int controls_size = ControlsPanel.btsz*4 + 2*pad;
	JCheckBox isShade;
	JRadioButton isPerspective;
	JRadioButton isOrth;
	private final static String predefined_functions[] = { 
		"-3 * cos(x) ",
		"3 * sin(x) * cos(y)",
		"3 * sin(x)^2 * cos(y)",
		"3 * sin(x^2) * cos(y)^2",
		"sin( x^2 + y^2 )", 
		"sin( x + y^2 )",
		"3 * sin( x * cos(y) )",
		"x * y^2" };
	private final static String predefined_dx[] = {
		"3.*sin(x)",
		"3.*cos(x)*cos(y)",
		"6.*sin(x)*cos(x)*cos(y)",
		"6.*cos(x*x)*x*cos(y)*cos(y)",
		"2.*x*cos(x*x+y*y)",
		"cos(x+y*y)",
		"3.*cos(x*cos(y))*cos(y)",
		"y^2" };
	private final static String predefined_dy[] = { 
		"0",
		"-3.*sin(x)*sin(y)",
		"-3.*sin(x)*sin(x)*sin(y)",
		"-6.*sin(x*x)*cos(y)*sin(y)",
		"2.*y*cos(x*x+y*y)", 
		"2.*y*cos(x+y*y)",
		"-3.*cos(x*cos(y))*x*sin(y)",
		"2*x*y" };	


	public StatesCapsule sC;
	private int _width = 400;
	private int _height = 400;

	public CRenderer renderer;
	public CRenderer second_render;
	private ImagePanel image_panel;
	private ControlsPanel control_panel;
	public Function f;
	public Function dx = null;
	public Function dz = null;
    private boolean axesFlag = false;
    
    DemoFrame() {
    	
    	super(default_functionCode,false, false, false, false);
   
    	try { // initialize function and partial derivatives
			Symbols symbols = new Symbols();
			setF(symbols.compile(default_functionCode));
			setDx(symbols.compile(default_dxCode));
			setDz(symbols.compile(default_dzCode));
		} catch (SyntaxException e) {
			return;
		}

		// create new state machine
		sC = new StatesCapsule(_width, _height);


		// create default main renderer
		setRenderer(new RendererFlat(get_height(), get_width(), getF()));
		// create second_renderer - in this cases - Axes renderer
		second_render = new RendererAxes(); //get_height(), mainW 				.get_width(), getF());

		// render you first image
		renderer.render(get_width(), get_height(), getSC());
		// put it on panel
		image_panel = new ImagePanel(
				renderer.getScreen() ,
				get_width(), get_height());
		
		setLayout(null);
		
		
		image_panel.setLocation(0,0);

		//setSize(get_height() + 100, get_width() + 100);
		//setLocation(200, 200);
	
		control_panel = new ControlsPanel(this);
		//control_panel.setLocation(image_panel.getWidth()-controls_size,0);
		control_panel.setSize(controls_size,500);
		control_panel.setLayout(null);
		control_panel.setVisible(true);	
		add(image_panel);
		add(control_panel);
	
		
		control_panel.set("reset",'b',pad,5);
		control_panel.set("-",'X',pad,35); //alpha
		control_panel.set("+",'x',pad+ControlsPanel.btsz,35);
		control_panel.set("Alpha",pad+2*ControlsPanel.btsz+5,35);
		control_panel.set("-",'Y',pad,65); //beta
		control_panel.set("+",'y',pad+ControlsPanel.btsz,65);
		control_panel.set("Beta",pad+2*ControlsPanel.btsz+5,65);
		control_panel.set("-",'-',pad,95); //distance
		control_panel.set("+",'+',pad+ControlsPanel.btsz,95);
		control_panel.set("Distance",pad+2*ControlsPanel.btsz+5,95);
		control_panel.set("-",'E',pad,125); //range
		control_panel.set("+",'e',pad+ControlsPanel.btsz,125);
		control_panel.set("Range",pad+2*ControlsPanel.btsz+5,125);
		control_panel.set("-",'l',pad,155); //light
		control_panel.set("+",'L',pad+ControlsPanel.btsz,155);
		control_panel.set("Light",pad+2*ControlsPanel.btsz+5,155);
		
		//control_panel.set("shading",'s',pad,185);
		JCheckBox checkShade = new JCheckBox("shading");
		checkShade.setSize(ControlsPanel.btsz*2,20);
		checkShade.setLocation(pad,185);
		checkShade.addActionListener(new ActionListener() {	public void actionPerformed(ActionEvent e) {				
			handleEvent('s');}});
		isShade = checkShade;
		control_panel.add(checkShade);
		
		//control_panel.set("projection",'o',pad,215);
	    JRadioButton orthogonal = new JRadioButton("orthogonal");
	    isOrth = orthogonal;
	    JRadioButton perspective = new JRadioButton("perspective");
	    isPerspective = perspective;
	    perspective.setSelected(true);
	    //Group the radio buttons.
	    ButtonGroup group = new ButtonGroup();
	    group.add(orthogonal);
	    group.add(perspective);
	    orthogonal.setLocation(pad,210);
	    perspective.setLocation(pad,225);
	    control_panel.add(perspective);
	    control_panel.add(orthogonal);
	    perspective.setSize(ControlsPanel.btsz*2,15);
	    orthogonal.setSize(ControlsPanel.btsz*2,15);
	    perspective.addActionListener(new ActionListener() {	public void actionPerformed(ActionEvent e) {				
			handleEvent('o');}});
	    orthogonal.addActionListener(new ActionListener() {	public void actionPerformed(ActionEvent e) {				
			handleEvent('O');}});
	    
		control_panel.set("load func.",'f',pad,245);
		control_panel.set("type func.",'q',pad,275);
		control_panel.set("axes",'!',pad,375);
		setVisible(true);

		
		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				repaint();
			}
		});
		
	}

	/**
	 * Used to issue 'repaint' order: 1) Will cause the default (and perhaps
	 * secondary) renderer to render 2) Will update the target image panel
	 */
	public void repaint() {
		if (null == sC)
			return;
		int _size = Math.min(this.getHeight() - 5, this.getWidth() - controls_size - 10);
		sC.setMaxx(_size - 1);
		sC.setMaxy(_size - 1);
		renderer.render(_size, _size, sC);
		second_render.render(_size, _size, sC);
		image_panel.init(renderer.getScreen(), _size, _size);
		if (axesFlag)
			image_panel.overdraw(second_render.getScreen(), _size, _size);
		control_panel.setLocation(_size+1,0);
		image_panel.repaint();
	}

	private void afterFReset(Function f, boolean isPredef) {
		this.setF(f);
		this.isShade.setSelected(false);
		this.isPerspective.setSelected(true);
		this.isOrth.setSelected(false);
		this.sC.setShade(false);
	
		
		if (! isPredef) {
			this.setDx(null);
			this.setDz(null);
			// NOTE: The derivatives must be set manually in current version of the
			// code!
		}
		this.setRenderer(new RendererFlat(this._height, this._width, f));
		this.repaint();
	}

	/**
	 * Handles function changing via GUI
	 */
	public void handleFunction() {
		Function prevF = this.f;
		Function lf = handleFunctionsAll("Please enter your function:\n", null)[0];
		if (lf == null) {
			this.f = prevF;
			return;
		}
		afterFReset(lf,false);

	}

	/**
	 * Handles choosing one of the predefined functions
	 */
	public void handleFunctionsPredef() {
		Function[] f_arr = handleFunctionsAll("Please choose your function:\n",
				predefined_functions);
		if (null == f_arr || null == f_arr[0]) 
			return;
		f = f_arr[0];
		dx = f_arr[1]; dz = f_arr[2];
		afterFReset(f,true);
	}

	/**
	 * Handle partial derivative input from user
	 * 
	 * @see handleFunctionsAll
	 */
	public void handleFunctionsDZ() {
		String title = this.getTitle();
		dz = handleFunctionsAll("Please enter your function (DY):\n", null)[0];
		this.setTitle(title);
	}

	/**
	 * Handle partial derivative input from user
	 * 
	 * @see handleFunctionsAll
	 */
	public void handleFunctionsDX() {
		String title = this.getTitle();
		dx = handleFunctionsAll("Please enter your function (DX):\n", null)[0];
		this.setTitle(title);
	}

	/**
	 * Generic function for handling function input from user
	 * 
	 * @param text
	 *            The dipslay string for the input box
	 * @param options
	 *            Optional field (may be null) stating predefined functions
	 * @return Constructed function
	 */
	public Function[] handleFunctionsAll(String text, Object[] options) {
		String fs = (String) JOptionPane.showInputDialog(this, text,
				"Functions Dialog", JOptionPane.PLAIN_MESSAGE, null, options,
				"");
		if (fs == null)
			return new Function[3];
		Function f = null;
		Function[] results = new Function[3];
		try {
			// compile new functions from string representation
			f = (new Symbols().compile(fs + "+0x+0y")); // "+0x+0y" is to match
														// arity of function to
														// 2
			if (options != null) { //if we are in predefined domain 
				int index = getIndexOfPredefinedFunction(fs);
				results[1] = (new Symbols().compile(predefined_dx[index] + "+0x+0y"));
				results[2] = (new Symbols().compile(predefined_dy[index] + "+0x+0y"));
				
			}
		} catch (SyntaxException e) {
			f = null;
			JOptionPane.showMessageDialog(null,
					"Failed setting function - check your input");
		}
		results[0] = f;
		sC = new StatesCapsule(get_height(), get_width()); 
		this.setTitle(fs);
		return results;
	}
	
	private int getIndexOfPredefinedFunction(String fs) {
		for (int i=0; i < predefined_functions.length; ++i) 
			if (fs.equals( predefined_functions[i]) )
				return i;
		return -1;
	}


	/**
	 * Static factory for renderers
	 * 
	 * @param sC
	 *            state machine encapsulating all environment variables
	 * @param _w
	 *            preferred width
	 * @param _h
	 *            preferred height
	 * @param f
	 *            default function
	 * @param dx
	 *            partial derivative_x
	 * @param dz
	 *            partial derivative_z
	 * @return constructed renderer object
	 */
	public static CRenderer createRenderer(StatesCapsule sC, int _w, int _h,
			Function f, Function dx, Function dz) {
		if (sC.getShade()) // if shading mode is on:
			if (sC.getNorm()) // either we use normals from dx,dz
				return new RendererNormal(_w, _h, f, dx, dz);
			else
				// or we do intens. interpolation
				return new RendererIntense(_w, _h, f, dx, dz);
		else
			// the third choice is to render flat and in monochrome
			return new RendererFlat(_w, _h, f);
	}
	
	private void act(JButton b, final char my_action) {
		b.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) {
			handleEvent(my_action);
			}});
	}
	


	public StatesCapsule getSC() {
		return sC;
	}

	public void setSC(StatesCapsule sc) {
		sC = sc;
	}

	public int get_width() {
		return _width;
	}

	public void set_width(int _width) {
		this._width = _width;
	}

	public int get_height() {
		return _height;
	}

	public void set_height(int _height) {
		this._height = _height;
	}


	public CRenderer getRenderer() {
		return renderer;
	}

	public void setRenderer(CRenderer rf) {
		this.renderer = rf;
	}

	public ImagePanel getImage_panel() {
		return image_panel;
	}

	public void setImage_panel(ImagePanel ip) {
		this.image_panel = ip;
	}

	public Function getF() {
		return f;
	}

	public void setF(Function f) {
		this.f = f;
	}

	public Function getDx() {
		return dx;
	}

	public void setDx(Function dx) {
		this.dx = dx;
	}

	public Function getDz() {
		return dz;
	}

	public void setDz(Function dz) {
		this.dz = dz;
	}


	
	public void handleEvent(char c) {
		switch (c) {
		case '!' :
			this.axesFlag = true;
			break;
		  case 's' : 
			  
				if (sC.getShade() == false) {
					if (dx == null) // if no partial derivative
						handleFunctionsDX();
					if (dx == null) {// if still no valid derivative
						isShade.setSelected(false);
						return;
					}

					if (dz == null) // if no partial derivative
						handleFunctionsDZ();
					if (dz == null) {// if still no valid derivative
						isShade.setSelected(false);
						return;
					}
				}
				
			sC.setShade(!sC.getShade()); 
			renderer = DemoFrame.createRenderer(sC, get_width(), get_height(), f, dx, dz);
		  	break;
		// case 'n' : sC.setNorm(!sC.getNorm()); break;
		// case 'r' : sC.setRidge(!sC.getRidge()); break;
		case 'o':
			sC.isPerspective = true;
			renderer = DemoFrame.createRenderer(sC, get_width(), get_height(), f, dx, dz);
		  	break;
		case 'O':
			sC.isPerspective = false;
			renderer = DemoFrame.createRenderer(sC, get_width(), get_height(), f, dx, dz);
		  	break;  	
		case 'f':
			handleFunctionsPredef();
		  	break;	
		case 'q':
			handleFunction();
		  	break;	 	
		case 'e':
			sC.setNeg_t_plane_limit(sC.getNeg_t_plane_limit()-2.0f);
			sC.setPos_t_plane_limit(sC.getPos_t_plane_limit()+2.0f);
			sC.setRADIUS(sC.getRADIUS()*1.25f);
			break;
		case 'E':
			sC.setNeg_t_plane_limit(sC.getNeg_t_plane_limit()+2.0f);
			sC.setPos_t_plane_limit(sC.getPos_t_plane_limit()-2.0f);
			sC.setRADIUS(sC.getRADIUS()/1.25f);
			break;
		case '+':
			sC.setDist_of_screen_from_origin(2 * sC
					.getDist_of_screen_from_origin());
			break;
		case '-':
			sC
					.setDist_of_screen_from_origin(sC
							.getDist_of_screen_from_origin() / 2);
			break;
		case 'D':
			sC.setDist(sC.getDist() + 0.25f);
			break;
		case 'd':
			sC.setDist(sC.getDist() - 0.25f);
			break; // Dist -= 0.25;
		case 'p':
			sC.setDist_veiwport_from_screen(sC.getDist_veiwport_from_screen() / 2);
			break;
		case 'P':
			sC.setDist_veiwport_from_screen(2 * sC
					.getDist_veiwport_from_screen());
			break;
		case 'k':
			sC
					.setDist_veiwport_from_screen(sC
							.getDist_veiwport_from_screen() / 2);
			break;
		case 'K':
			sC
					.setDist_veiwport_from_screen(sC
							.getDist_veiwport_from_screen() * 2);
			break;
		case 'x':
			sC.setAngle_Alpha(sC.getAngle_Alpha() + sC.Angle_step);
			break;
		case 'X':
			sC.setAngle_Alpha(sC.getAngle_Alpha() - sC.Angle_step);
			break;
		case 'Y':
			sC.setAngle_Beta(sC.getAngle_Beta() + sC.Angle_step);
			break;
		case 'y':
			sC.setAngle_Beta(sC.getAngle_Beta() - sC.Angle_step);
			break;
		case 'L':
			sC.setIp(sC.getIp() + 0.125f);
			break;
		case 'l':
			sC.setIp(sC.getIp() - 0.125f);
			break;
		case 'T':
			sC.setThldn(sC.getThldn() * 2.0f);
			break;
		case 't':
			sC.setThldn(sC.getThldn() * 0.5f);
			break;
		case 'b':
			setSC(new StatesCapsule(get_height(), get_width()));
			this.isShade.setSelected(false);
			this.isPerspective.setSelected(true);
			this.isOrth.setSelected(false);
			renderer = DemoFrame.createRenderer(sC, get_width(), get_height(), f, dx, dz);
			break;			
		case 'I':
			sC.setThin(sC.getThin() * 2.0f);
			break;
			
		case 'a':
			axesFlag = !axesFlag; 
			break;				
		case 'i':
			sC.setThin(sC.getThin() * 2.0f);
			break;
		}
		;
		repaint();
	}
}
